package com.ty.web.monitor;

import boofcv.struct.image.GrayF32;
import com.ty.cache.LocalCache;
import com.ty.constant.enums.SocketMessageType;
import com.ty.model.ImageInfo;
import com.ty.model.SocketMessage;
import com.ty.model.TyMessage;
import com.ty.utils.DataUtil;
import com.ty.utils.HttpUtil;
import com.ty.utils.ImageUtil;
import com.ty.web.push.TPush;
import com.ty.web.spring.SpringContextHolder;
import com.ty.web.spring.config.properties.TyProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.lang3.StringUtils;

import java.awt.image.BufferedImage;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * 图片文件事件回调对象
 *
 * @Author Tommy
 * @Date 2024/4/19
 */
@Slf4j
public class ImageFileCallbackEvent extends FileAlterationListenerAdaptor {

    // 图片相似度阈值
    private double threshold = 0.983d;

    // 消息推送对象
    private final TPush tpush;

    // 缓存对象
    private final LocalCache cache;

    // 过期图片的判定期限（单位：分钟）
    private Integer expiredPeriod = 10;

    // WS广播消息地址
    private String destination;

    // 时间格式化对象
    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");

    // 项目配置属性
    private TyProperties tyProperties;

    /**
     * 实例化事件回调对象
     */
    public ImageFileCallbackEvent() {
        this.cache = SpringContextHolder.getBean(LocalCache.class);
        this.tpush = SpringContextHolder.getBean(TPush.class);

        tyProperties = SpringContextHolder.getBean(TyProperties.class);
        this.destination = tyProperties.getDestination();
        if (null != tyProperties.getThreshold()) {
            this.threshold = tyProperties.getThreshold();
        }
    }

    /**
     * 文件创建事件
     *
     * @param file The file created (ignored)
     */
    @Override
    public void onFileCreate(File file) {
        this.handle(file);
    }

    /**
     * 文件变化事件
     *
     * @param file The file changed (ignored)
     */
    @Override
    public void onFileChange(File file) {
        this.handle(file);
    }

    /**
     * 图片逻辑处理
     */
    void handle(File file) {
        // 获取摄像头编号
        String sn = cameraSN(file);

        try {
            // 判定图片是否过期
            if (isExpired(file)) {
                log.warn("图片过期：{}", file.getPath());
                return;
            }

            // 判定图片是否缺损
            BufferedImage img = ImageUtil.read(file);
            GrayF32 gray1 = ImageUtil.toGray(img);
            if (ImageUtil.isLack(gray1, tyProperties.getLossThreshold())) {
                log.warn("图片缺损：{}", file.getPath());
                return;
            }

            // 若存在历史图片记录，则对比相似度
            double similarity = 0d;
            long duration = 0;
            String lastPath = cache.get(sn);
            if (StringUtils.isNotBlank(lastPath)) {
                File lastFile = new File(lastPath);
                if (lastFile.exists()) {
                    long begin = System.currentTimeMillis();
                    // 计算图片相似度
                    BufferedImage lastImg = ImageUtil.read(lastFile);
                    if (ImageUtil.sameSize(img, lastImg)) {
                        GrayF32 gray2 = ImageUtil.toGray(lastImg);
                        similarity = ImageUtil.similarity(gray1, gray2);
                        duration = Math.round(System.currentTimeMillis() - begin);

                        // 若图片相似，则忽略下面的处理
                        if (similarity >= this.threshold) {
                            log.debug("{} 相似度 = {} 耗时: {}ms", sn, similarity, duration);
                            return;
                        }
                    }
                }
            }

            // 构建图片信息
            ImageInfo imageInfo = this.build(sn, similarity, file);

            // 推送图片信息
            tpush.send(destination, SocketMessage.create(SocketMessageType.NOTICE, imageInfo));

            // 推送图片到AI服务
            push2AI(imageInfo);

            // 打印日志
            log.info("文件变化：{}\t{}\t{}\t耗时: {}ms", sn, similarity, file.getPath(), duration);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            // 保存本次图片路径到缓存
            cache.add(sn, file.getPath());
        }
    }

    /**
     * 判断图片是否过期
     */
    boolean isExpired(File file) {
        long period = System.currentTimeMillis() - file.lastModified();
        return period - this.expiredPeriod * 60 * 1000 > 0;
    }

    /**
     * 获取摄像头编号
     */
    String cameraSN(File file) {
        return file.getParentFile().getName();
    }

    /**
     * 获取文件后缀名
     */
    String suffix(File file) {
        return FilenameUtils.getExtension(file.getName());
    }

    /**
     * 获取文件最后修改的时间
     */
    Date lastModified(File file) {
        return new Date(file.lastModified());
    }

    /**
     * 格式化时间
     */
    String formatDate(Date date) {
        return dateFormat.format(date);
    }

    /**
     * 构建 ImageInfo 对象
     */
    ImageInfo build(String sn, double similarity, File file) throws Exception {
        Date lastModified = lastModified(file);
        String name = formatDate(lastModified);
        ImageInfo imageInfo = new ImageInfo();
        imageInfo.setId(sn + name);
        imageInfo.setCameraSN(sn);
        imageInfo.setLastModified(file.lastModified());
        imageInfo.setSimilarity(similarity);
        imageInfo.setName(name + "_" + similarity + "." + suffix(file));
        imageInfo.setPath("/Changed/" + sn + "/" + imageInfo.getName());

        // 复制文件到指定目录
        String vcrootPath = file.getParentFile().getParentFile().getPath();
        File targetFile = new File(vcrootPath + imageInfo.getPath());
        FileUtils.copyFile(file, targetFile);
        return imageInfo;
    }

    /**
     * 将图片推送到AI服务
     */
    void push2AI(ImageInfo imageInfo) {
        if (tyProperties.getEnableAIPush()) {
            Map<String, Object> message = TyMessage.build(imageInfo.getCameraSN(), tyProperties.getPubNetAddr() + imageInfo.getPath());
            String result = HttpUtil.postForJson(tyProperties.getAiServerUrl(), message, HttpUtil.headers());
            log.info("推送图片到AI服务 {} {}\r\n{}\r\n响应: {}", imageInfo.getCameraSN(), tyProperties.getAiServerUrl(), DataUtil.toJSON(message), result);
        } else {
            log.warn("没有开启AI推送服务");
        }
    }
}
